package com.xz.usercenter.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xz.usercenter.common.ErrorCode;
import com.xz.usercenter.exception.BusinessException;
import com.xz.usercenter.mapper.TeamMapper;
import com.xz.usercenter.model.domain.Team;
import com.xz.usercenter.model.domain.User;
import com.xz.usercenter.model.domain.UserTeam;
import com.xz.usercenter.model.dto.TeamQuery;
import com.xz.usercenter.model.enums.TeamStatusEnum;
import com.xz.usercenter.model.request.TeamJoinRequest;
import com.xz.usercenter.model.request.TeamQuitRequest;
import com.xz.usercenter.model.request.TeamUpdateRequest;
import com.xz.usercenter.model.vo.TeamUserVO;
import com.xz.usercenter.model.vo.UserVO;
import com.xz.usercenter.service.TeamService;
import com.xz.usercenter.service.UserService;
import com.xz.usercenter.service.UserTeamService;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;

import static com.xz.usercenter.constant.LockConstant.JOIN_TEAM_LOCK;

/**
 * @author xuzhou
 * @description 针对表【team(队伍)】的数据库操作Service实现
 * @createDate 2024-06-09 10:22:36
 */
@Service
public class TeamServiceImpl extends ServiceImpl<TeamMapper, Team>
        implements TeamService {

    /**
     * 用户 - 队伍 关联表
     */
    @Resource
    private UserTeamService userTeamService;

    @Resource
    private UserService userService;

    @Resource
    private RedissonClient redissonClient;

    // 添加抢锁最大尝试次数常量
    private static final int MAX_LOCK_ATTEMPTS = 5;

    @Override
    @Transactional(rollbackFor = Exception.class) // 事务注解
    public long addTeam(Team team, User loginUser) {
        //1. 请求参数是否为空？
        if (team == null) {
            throw new BusinessException(ErrorCode.NULL_ERROR);
        }
        //2. 是否登录，未登录不允许创建
        if (loginUser == null) {
            throw new BusinessException(ErrorCode.NOT_LOGIN);
        }
        final long userId = loginUser.getId();
        //3. 校验信息
        //  a. 队伍人数 > 1 且 <= 20
        int maxNum = Optional.ofNullable(team.getMaxNum()).orElse(0);
        if (maxNum < 1 || maxNum > 20) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍人数不符合要求");
        }
        //  b. 队伍标题 <= 20
        String name = team.getTeamName();
        if (StringUtils.isBlank(name) || name.length() > 20) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍标题不满足要求");
        }
        //  c. 描述 <= 512
        String description = team.getDescription();
        if (StringUtils.isNotBlank(description) && description.length() > 512) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍描述过长");
        }
        // 队伍状态必须正确
        if (team.getTeamStatus() != null && !TeamStatusEnum.getValues().contains(team.getTeamStatus())) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍状态错误");
        }
        //  d. status 是否公开（int）不传默认为 0（公开）
        int status = Optional.ofNullable(team.getTeamStatus()).orElse(0);
        TeamStatusEnum statusEnum = TeamStatusEnum.getEnumByValue(status);
        if (statusEnum == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍状态不满足要求");
        }
        //  e. 如果 status 是加密状态，一定要有密码，且密码 <= 32
        String password = team.getTeamPassword();
        if (TeamStatusEnum.SECRET.equals(statusEnum)) {
            if (StringUtils.isBlank(password) || password.length() > 32) {
                throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码设置不正确");
            }
        }
        //  f. 超时时间 > 当前时间
        Date expireTime = team.getExpireTime();
        if (expireTime == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "超时时间不能为空");
        }
        if (new Date().after(expireTime)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "超时时间 大于 当前时间");
        }
        //  g. 校验用户最多创建 5 个队伍
        QueryWrapper<Team> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("user_id", userId);
        long hasTeamNum = this.count(queryWrapper);
        if (hasTeamNum >= 5) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户最多创建 5 个队伍");
        }
        //4. 插入队伍信息到队伍表
        team.setId(null);
        team.setUserId(userId);
        team.setCreatorId(userId);
        boolean result = this.save(team);
        Long teamId = team.getId();
        if (!result || teamId == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "创建队伍失败");
        }
        //5. 插入用户 => 队伍关系到关系表 自己创建的队伍自己也算在里面
        UserTeam userTeam = new UserTeam();
        userTeam.setUserId(userId);
        userTeam.setTeamId(teamId);
        userTeam.setJoinTime(new Date());
        result = userTeamService.save(userTeam);
        if (!result) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "创建队伍失败");
        }
        return teamId;
    }


    // todo 关联查询已加入队伍的用户信息（可能会很耗费性能，建议大家用自己写 SQL 的方式实现）
    @Override
    public IPage<TeamUserVO> pageTeams(TeamQuery teamQuery, boolean isAdmin, Page<Team> page) {
        QueryWrapper<Team> queryWrapper = new QueryWrapper<>();
        // 组合查询条件
        if (teamQuery != null) {
            Long id = teamQuery.getId();
            if (id != null && id > 0) {
                queryWrapper.eq("id", id);
            }
            List<Long> idList = teamQuery.getIdList();
            if (CollectionUtils.isNotEmpty(idList)) {
                queryWrapper.in("id", idList);
            }
            // 可以通过某个关键词同时对名称和描述查询
            String searchText = teamQuery.getSearchText();
            if (StringUtils.isNotBlank(searchText)) {
                queryWrapper.and(qw -> qw.like("team_name", searchText).or().like("description", searchText));
            }
            // 查询队伍名称
            String name = teamQuery.getName();
            if (StringUtils.isNotBlank(name)) {
                queryWrapper.like("team_name", name);
            }
            // 查询队伍描述
            String description = teamQuery.getDescription();
            if (StringUtils.isNotBlank(description)) {
                queryWrapper.like("description", description);
            }
            // 查询最大人数相等的
            Integer maxNum = teamQuery.getMaxNum();
            if (maxNum != null && maxNum > 0) {
                queryWrapper.eq("max_num", maxNum);
            }
            Long userId = teamQuery.getUserId();
            // 根据创建人来查询
            if (userId != null && userId > 0) {
                queryWrapper.eq("user_id", userId);
            }
            // 根据状态来查询
            Integer status = teamQuery.getTeamStatus();
            TeamStatusEnum statusEnum = TeamStatusEnum.getEnumByValue(status);
            if (statusEnum == null) {
                statusEnum = TeamStatusEnum.PUBLIC;
            }
            // 不是管理员，不能查询非公开
            if (!isAdmin && statusEnum.equals(TeamStatusEnum.PRIVATE)) {
                throw new BusinessException(ErrorCode.NO_AUTH);
            }
            if (!isAdmin) {
                queryWrapper.eq("team_status", statusEnum.getValue());
            }
        }
        // 不展示已过期的队伍
        // expireTime is null or expireTime > now()
        queryWrapper.and(qw -> qw.gt("expire_time", new Date()).or().isNull("expire_time"));
        IPage<Team> teamList = this.page(page, queryWrapper);
        // 检查转换后的队伍列表是否为空
        if (CollectionUtils.isEmpty(teamList.getRecords())) {
            // 返回空列表
            return new Page<>();
        }

        List<TeamUserVO> teamUserVOList = new ArrayList<>();
        // 关联查询创建人的用户信息
        for (Team team : teamList.getRecords()) {
            Long userId = team.getUserId();
            if (userId == null) {
                continue;
            }
            User user = userService.getById(userId);
            TeamUserVO teamUserVO = new TeamUserVO();
            BeanUtils.copyProperties(team, teamUserVO);
            // 脱敏用户信息
            if (user != null) {
                UserVO userVO = new UserVO();
                BeanUtils.copyProperties(user, userVO);
                teamUserVO.setCreateUser(userVO);
            }
            teamUserVOList.add(teamUserVO);
        }

        Page<TeamUserVO> teamUserVOPage = new Page<>();
        BeanUtils.copyProperties(teamList, teamUserVOPage);
        teamUserVOPage.setRecords(teamUserVOList); //获取分页记录

        return teamUserVOPage;
    }

    @Override
    public boolean updateTeam(TeamUpdateRequest teamUpdateRequest, User logininUser) {
        if (teamUpdateRequest == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        Long id = teamUpdateRequest.getId();
        if (id == null || id <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        Team oldTeam = this.getById(id);
        if (oldTeam == null) {
            throw new BusinessException(ErrorCode.NULL_ERROR, "队伍不存在");
        }
        // 如果用户传入的新值 teamUpdateRequest 和 老值 数据库中的值 一致，就不用 update 了
        boolean isUpdateNeeded = isUpdateNeeded(teamUpdateRequest, oldTeam);
        // 如果没有需要更新的字段，则直接返回 true，表示无需更新
        if (!isUpdateNeeded) {
            return true;
        }

        // 只有管理员 或者 队伍的创建者可以修改
        if (!oldTeam.getUserId().equals(logininUser.getId()) && !userService.isAdmin(logininUser)) {
            throw new BusinessException(ErrorCode.NO_AUTH);
        }

        // 加密房间必须设置密码 且密码必须符合要求
        TeamStatusEnum statusEnum = TeamStatusEnum.getEnumByValue(teamUpdateRequest.getTeamStatus());
        if (TeamStatusEnum.SECRET.equals(statusEnum)) {
            if (StringUtils.isBlank(teamUpdateRequest.getTeamPassword())) {
                throw new BusinessException(ErrorCode.PARAMS_ERROR, "加密房间必须设置密码");
            }
            if (teamUpdateRequest.getTeamPassword().length() < 32) {
                throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码设置不正确");
            }
        }

        // 更改的状态码必须正确
        if (teamUpdateRequest.getTeamStatus() != null && !TeamStatusEnum.getValues().contains(teamUpdateRequest.getTeamStatus())) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍状态错误");
        }

        //todo 选择更新
        Team updateTeam = new Team();
        BeanUtils.copyProperties(teamUpdateRequest, updateTeam);

        return this.updateById(updateTeam);

        // 队伍名称不允许重复的代码  但是我允许重复 😀
//        if (StringUtils.isNotBlank(teamUpdateRequest.getTeamName()) && !teamUpdateRequest.getTeamName().equals(oldTeam.getTeamName())){
//            QueryWrapper<Team> queryWrapper = new QueryWrapper<>();
//            queryWrapper.eq("team_name", teamUpdateRequest.getTeamName());
//            long count = this.count(queryWrapper);
//            if (count > 0){
//                throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍名称已存在");
//            }
//            oldTeam.setTeamName(teamUpdateRequest.getTeamName());
//        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class) // 事务注解
    public boolean joinTeam(TeamJoinRequest teamJoinRequest, User loginUser) {
        if (teamJoinRequest == null) {
            throw new BusinessException(ErrorCode.NULL_ERROR);
        }
        // 队伍必须存在
        Long teamId = teamJoinRequest.getTeamId();
        if (teamId == null || teamId <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        Team team = this.getById(teamId);
        if (team == null) {
            throw new BusinessException(ErrorCode.NULL_ERROR, "队伍不存在");
        }
        Date expireTime = team.getExpireTime();
        // 只能加入未过期的队伍
        if (team.getExpireTime() != null && expireTime.before(new Date())) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍已过期");
        }
        // 禁止加入私有队伍
        Integer teamStatus = team.getTeamStatus();
        TeamStatusEnum teamStatusEnum = TeamStatusEnum.getEnumByValue(teamStatus);
        if (TeamStatusEnum.PRIVATE.equals(teamStatusEnum)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "禁止加入私有队伍");
        }
        // 如果加入的队伍是加密的，必须密码匹配才可以
        if (TeamStatusEnum.SECRET.equals(teamStatusEnum)) {
            // 队伍密码
            String teamPassword = teamJoinRequest.getTeamPassword();
            if (StringUtils.isBlank(teamPassword) && !teamPassword.equals(team.getTeamPassword())) {
                throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码错误");
            }
        }
        // 该用户已加入的队伍数量
        long userId = loginUser.getId();
        // 只有一个线程能获取到锁
        // 获取锁
        RLock lock = redissonClient.getLock(JOIN_TEAM_LOCK);
        try {
            // 抢到锁并执行，限制尝试次数
            int tryCount = 0;
            // 抢到锁并执行
            while (tryCount < MAX_LOCK_ATTEMPTS) {
                // 尝试获取锁，参数表示无限期等待锁，不设置超时时间
                if (lock.tryLock(0, -1, TimeUnit.MILLISECONDS)) {
                    try {
                        // 用户最多加入 5 个队伍
                        QueryWrapper<UserTeam> userTeamQueryWrapper = new QueryWrapper<>();
                        userTeamQueryWrapper.eq("user_id", userId);
                        long hasJoinNum = userTeamService.count(userTeamQueryWrapper);
                        if (hasJoinNum >= 5) {
                            throw new BusinessException(ErrorCode.PARAMS_ERROR, "最多创建和加入 5 个队伍");
                        }
                        // 不能重复加入已加入的队伍 (幂等性)  判断 用户队伍关联表 中是否已经有同样的人加入同样的队伍
                        userTeamQueryWrapper = new QueryWrapper<>();
                        userTeamQueryWrapper.eq("team_id", teamId);
                        userTeamQueryWrapper.eq("user_id", userId);
                        long hasUserJoinTeam = userTeamService.count(userTeamQueryWrapper);
                        if (hasUserJoinTeam > 0) {
                            throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户已加入该队伍");
                        }
                        // 只能加入人数未满的队伍
                        long teamHasJoinNum = this.countTeamUserByTeamId(teamId);
                        if (teamHasJoinNum >= team.getMaxNum()) {
                            throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍已满");
                        }
                        // 新增 用户 - 队伍 关联信息
                        UserTeam userTeam = new UserTeam();
                        userTeam.setUserId(userId);
                        userTeam.setTeamId(teamId);
                        userTeam.setJoinTime(new Date());
                        return userTeamService.save(userTeam);
                    } finally {
                        // 只能释放自己的锁
                        // 只有当前线程持有的锁才能被释放
                        if (lock.isHeldByCurrentThread()) {
                            System.out.println("unLock: " + Thread.currentThread().getId());
                            lock.unlock();
                        }
                    }
                }
                tryCount++;
                // 在尝试获取锁之间可以添加一定的等待时间以减少冲突，
                // 等待策略: 在每次尝试获取锁失败后，代码会休眠50毫秒，这有助于减少锁的竞争，避免过多的线程同时竞争锁资源。
                Thread.sleep(50);
            }
            // 如果超出尝试次数，抛出异常或返回失败
            throw new BusinessException(ErrorCode.RETRY_ERROR, "请稍后再试");
        } catch (InterruptedException e) {
            log.error("recommendUser redisson lock error", e);
            // 加入失败
            return false;
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean quitTeam(TeamQuitRequest teamQuitRequest, User loginUser) {
        // 1. 校验请求参数
        if (teamQuitRequest == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        Long teamId = teamQuitRequest.getTeamId();
        if (teamId == null || teamId <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        // 2. 校验队伍是否存在
        Team team = this.getById(teamId);
        if (team == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍不存在");
        }
        Long userId = loginUser.getId();
        if (userId == null || userId <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        // 3. 校验我是否加入了队伍   防止没有加入队伍的情况 退出队伍
        UserTeam queryUserTeam = new UserTeam();
        queryUserTeam.setUserId(userId);
        queryUserTeam.setTeamId(teamId);
        QueryWrapper<UserTeam> queryWrapper = new QueryWrapper<>(queryUserTeam);
        long count = userTeamService.count(queryWrapper);
        if (count == 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "未加入队伍");
        }
        // 4. 退出逻辑
        // 当前队伍人数
        long teamHasJoinNum = this.countTeamUserByTeamId(teamId);
        // 队伍只剩一人, 解散
        if (teamHasJoinNum == 1) {
            // 删除队伍
            this.removeById(teamId);
        } else {
            // 队伍至少还有两人
            // 如果退出的是队长
            if (Objects.equals(team.getUserId(), userId)) {
                // 把队伍转移给最早加入的用户
                // 1. 查询已加入队伍的所有用户和加入时间 (用户 队伍 关联表 userTeamService 的id 即加入是时间早晚)
                QueryWrapper<UserTeam> userTeamQueryWrapper = new QueryWrapper<>();
                userTeamQueryWrapper.eq("team_id", teamId);
                // 第一个是队长 第二个是除队长外最找加入的人
                userTeamQueryWrapper.last("order by id asc limit 2");
                List<UserTeam> userTeamList = userTeamService.list(userTeamQueryWrapper);
                if (CollectionUtils.isEmpty(userTeamList) || userTeamList.size() <= 1) {
                    throw new BusinessException(ErrorCode.SYSTEM_ERROR);
                }
                // 取出 第二个 除队长外最找加入的人
                UserTeam nextUserTeam = userTeamList.get(1);
                Long nextTeamLeaderId = nextUserTeam.getUserId();
                // 更新队伍的队长
                Team updateTeam = new Team();
                updateTeam.setId(teamId);
                updateTeam.setUserId(nextTeamLeaderId);
                boolean result = this.updateById(updateTeam);
                if (!result) {
                    throw new BusinessException(ErrorCode.SYSTEM_ERROR, "更新队伍队长失败");
                }
            }
        }
        // 移除关联关系
        return userTeamService.remove(queryWrapper);
    }

    /**
     * 获取某队伍当前人数
     *
     * @param teamId 队伍id
     * @return
     */
    private long countTeamUserByTeamId(long teamId) {
        QueryWrapper<UserTeam> userTeamQueryWrapper = new QueryWrapper<>();
        userTeamQueryWrapper.eq("team_id", teamId);
        return userTeamService.count(userTeamQueryWrapper);
    }

    /**
     * 解散队伍
     *
     * @param id
     * @param loginUser
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean deleteTeam(long id, User loginUser) {
        if (id <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        // 1. 校验队伍是否存在
        Team team = getTeamById(id);
        long teamId = team.getId();
        // 2. 校验你是不是队伍的队长
        if (!Objects.equals(team.getUserId(), loginUser.getId())) {
            throw new BusinessException(ErrorCode.NO_AUTH, "无权限!");
        }
        // 3. 移除所有加入队伍的关联信息
        QueryWrapper<UserTeam> userTeamQueryWrapper = new QueryWrapper<>();
        userTeamQueryWrapper.eq("team_id", teamId);
        boolean result = userTeamService.remove(userTeamQueryWrapper);
        if (!result) {
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "删除队伍关联信息失败");
        }
        // 4. 删除队伍
        return this.removeById(teamId);
    }

    /**
     * 根据 id 获取队伍信息
     *
     * @param teamId
     * @return
     */
    private Team getTeamById(Long teamId) {
        if (teamId == null || teamId <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        Team team = this.getById(teamId);
        if (team == null) {
            throw new BusinessException(ErrorCode.NULL_ERROR, "队伍不存在");
        }
        return team;
    }


    /**
     * 判断 用户传入的新值 teamUpdateRequest 和 老值 数据库中的值 是否 一致
     *
     * @param teamUpdateRequest
     * @param oldTeam
     * @return
     */
    private static boolean isUpdateNeeded(TeamUpdateRequest teamUpdateRequest, Team oldTeam) {
        boolean isUpdateNeeded = false;

        if (!Objects.equals(oldTeam.getTeamName(), teamUpdateRequest.getTeamName())) {
            isUpdateNeeded = true;
        }
        if (!Objects.equals(oldTeam.getDescription(), teamUpdateRequest.getDescription())) {
            isUpdateNeeded = true;
        }
        if (!Objects.equals(oldTeam.getTeamStatus(), teamUpdateRequest.getTeamStatus())) {
            isUpdateNeeded = true;
        }
        if (!Objects.equals(oldTeam.getTeamPassword(), teamUpdateRequest.getTeamPassword())) {
            isUpdateNeeded = true;
        }
        if (!Objects.equals(oldTeam.getExpireTime(), teamUpdateRequest.getExpireTime())) {
            isUpdateNeeded = true;
        }

        return isUpdateNeeded;
    }
}




